namespace Guia.Locator
{
    using System;
    using System.Diagnostics.Contracts;
    using System.Threading;
    using System.Windows.Automation;
    using Guia.Config;
    using Guia.Core;
    using Guia.Elements;

    /// <summary>
    /// This class encapsulates the access to the UIA library. It is used to find and retrieve AutomationElements
    /// </summary>
    public static class AutomationElementFinder
    {
        /// <summary>
        /// Time to find an element.
        /// </summary>
        private static readonly int Timeout;

        /// <summary>
        /// The configuration.
        /// </summary>
        private static readonly IConfig Configuration;

        /// <summary>
        /// Time to sleep after finding-call.
        /// TODO: Idealer sleep wert herausfinden
        /// </summary>
        private const int FindSleep = 100;

        /// <summary>
        /// Initializes static members of the <see cref="AutomationElementFinder"/> class.
        /// </summary>
        static AutomationElementFinder()
        {
            Configuration = AppConfigImpl.Instance;
            Timeout = Configuration.ElementTimeout;
        }

        /// <summary>
        /// Returns the first AutomationElement that matches the given SearchCondition
        /// </summary>
        /// <param name="parent">Parent Element. The search starts from there</param>
        /// <param name="searchCondition">The SearchCondition</param>
        /// <returns>The first found AutomationElement that matches the SearchCondition</returns>
        /// <exception cref="ElementNotFoundException"/>
        /// <exception cref="ArgumentNullException">This exception is thrown if parent or searchCondition is null.</exception>
        public static AutomationElement FindFirst(UiElement parent, SearchCondition searchCondition)
        {
            Contract.Requires<ArgumentNullException>(parent != null);
            Contract.Requires<ArgumentNullException>(searchCondition != null);
            Condition condition = searchCondition.CreateCondition();
            AutomationElement root = parent.AutomationElement;
            AutomationElement result = null;
            Action doAction = () =>
                                  {
                                      while (result == null)
                                      {
                                          try
                                          {
                                              result = root.FindFirst(searchCondition.Scope, condition);
                                          }
                                          catch (InvalidOperationException)
                                          {
                                              // Try again
                                          }

                                          Delay.ForMilliseconds(FindSleep);
                                      }
                                  };
            Action timeoutAction = () =>
                                       {
                                           throw new ElementNotFoundException("Could not find AutomationElement with the SearchCondition " + condition);
                                       };
            Clock.Run(doAction, timeoutAction, Timeout);
            return result;
        }

        /// <summary>
        /// Returns all AutomationElements that match the given SearchCondition
        /// </summary>
        /// <param name="parent">Parent Element. The search starts from there</param>
        /// <param name="searchCondition">The SearchCondition</param>
        /// <returns>All AutomationElements that match the SearchCondtion</returns>
        /// <exception cref="ElementNotFoundException"/>
        /// <exception cref="ArgumentNullException">This exception is thrown if parent or searchCondition is null.</exception>
        public static AutomationElementCollection FindAll(UiElement parent, SearchCondition searchCondition)
        {
            Contract.Requires<ArgumentNullException>(parent != null);
            Contract.Requires<ArgumentNullException>(searchCondition != null);
            Condition condition = searchCondition.CreateCondition();
            AutomationElement root = parent.AutomationElement;
            AutomationElementCollection result = null;
            Action doAction = () =>
            {
                while (result == null || result.Count == 0)
                {
                    try
                    {
                        result = root.FindAll(searchCondition.Scope, condition);
                    }
                    catch (InvalidOperationException)
                    {
                        // try again
                    }

                    Delay.ForMilliseconds(FindSleep);
                }
            };
            Action timeoutAction = () =>
            {
                throw new ElementNotFoundException("Could not find any AutomationElement with the SearchCondition " + condition);
            };
            Clock.Run(doAction, timeoutAction, Timeout);
            return result;
        }

        /// <summary>
        /// Returns all descendant elements of the given UiElement
        /// </summary>
        /// <param name="parent">Parent Element. All descendant elements are returned</param>
        /// <returns>All descendant AutomationElements</returns>
        /// <exception cref="ElementNotFoundException"/>
        /// <exception cref="ArgumentNullException">This exception is thrown if parent is null.</exception>
        public static AutomationElementCollection FindAll(UiElement parent)
        {
            Contract.Requires<ArgumentNullException>(parent != null);
            SearchCondition searchCondition = SearchCondition.FromAutomationCondition(Condition.TrueCondition);
            return FindAll(parent, searchCondition);
        }
    }
}